package com.meshnetics.gb.stack.mac.primitives;

import com.meshnetics.gb.stack.core.event.StackEventType;
import com.meshnetics.gb.stack.core.event.StackIndication;
import com.meshnetics.gb.stack.core.layer.StackLayer;
import com.meshnetics.gb.stack.mac.RadioMACStackLayer;
import com.meshnetics.gb.stack.mac.type.DataAddress;
import com.meshnetics.gb.stack.mac.type.TypeDataAddress;
import com.meshnetics.gb.type.impl.ByteUtils;
import com.meshnetics.gb.type.impl.zcl.*;

import java.nio.ByteBuffer;

/**
 * WRITE DESCRIPTION HERE
 *
 * @author AKovylin
 */
public class MACDataIndication extends StackIndication {
    public static final byte CODE = 0x42;

    private final DataAddress srcAddr;
    private final DataAddress dstAddr;
    private final UInt msduLength;
    private final Str msdu;
    private final UInt mpduLinkQuality;
    private final Bitmap secAndAcl;

    public MACDataIndication(ByteBuffer bb, RadioMACStackLayer layer) {
        super("macDataIndication", StackEventType.LDE_TYPE, null, layer);
        ByteUtils.ensureNotNull(bb);
        this.srcAddr = (DataAddress) TypeDataAddress.TYPE.deserialize(bb);
        this.dstAddr = (DataAddress) TypeDataAddress.TYPE.deserialize(bb);
        this.msduLength = (UInt) TypeUInt.UINT_8.deserialize(bb);
        int size = ((Number) msduLength.toObject()).intValue();
        if (size < 0 || size > 102) {
            throw new IllegalArgumentException("invalid msduLength");
        }
        this.msdu = (Str) TypeStr.STRING_OCTET.deserialize(bb, size, false);
        this.mpduLinkQuality = (UInt) TypeUInt.UINT_8.deserialize(bb);
        this.secAndAcl = (Bitmap) TypeBitmap.BITMAP_8.deserialize(bb);
        if (bb.remaining() > 0) {
            throw new IllegalArgumentException("unexpected content of ByteBuffer, " + bb.remaining() + " bytes left");
        }
    }

    public MACDataIndication(DataAddress srcAddr, DataAddress dstAddr, byte[] msdu,
                             int mpduLinkQuality, boolean securityUse, int aclEntry) {
        this(srcAddr, dstAddr, msdu, (byte) (mpduLinkQuality & 0xff), securityUse, (byte) (aclEntry & 0xff));
    }

    public MACDataIndication(DataAddress srcAddr, DataAddress dstAddr, byte[] msdu,
                             byte mpduLinkQuality, boolean securityUse, byte aclEntry) {
        this(srcAddr, dstAddr, msdu, new Byte(mpduLinkQuality), Boolean.valueOf(securityUse), new Byte(aclEntry));
    }

    public MACDataIndication(DataAddress srcAddr, DataAddress dstAddr, byte[] msdu,
                             Byte mpduLinkQuality, Boolean securityUse, Byte aclEntry) {
        this(srcAddr, dstAddr, (Str) TypeStr.STRING_OCTET.fromObject(msdu),
                (UInt) TypeUInt.UINT_8.fromObject(mpduLinkQuality),
                Utils.createBitmapSecAndAcl(securityUse.booleanValue(), aclEntry.intValue()));
    }

    public MACDataIndication(DataAddress srcAddr, DataAddress dstAddr, Str msdu,
                             UInt mpduLinkQuality, boolean securityUse, int aclEntry) {
        this(srcAddr, dstAddr, msdu, mpduLinkQuality, securityUse, (byte) (aclEntry & 0xff));
    }

    public MACDataIndication(DataAddress srcAddr, DataAddress dstAddr, Str msdu,
                             UInt mpduLinkQuality, boolean securityUse, byte aclEntry) {
        this(srcAddr, dstAddr, msdu, mpduLinkQuality, Utils.createBitmapSecAndAcl(securityUse, aclEntry));
    }

    public MACDataIndication(DataAddress srcAddr, DataAddress dstAddr, Str msdu,
                             UInt mpduLinkQuality, Boolean securityUse, Byte aclEntry) {
        this(srcAddr, dstAddr, msdu, mpduLinkQuality,
                Utils.createBitmapSecAndAcl(securityUse.booleanValue(), aclEntry.intValue()));
    }

    public MACDataIndication(DataAddress srcAddr, DataAddress dstAddr, Str msdu,
                             UInt mpduLinkQuality, Boolean securityUse, Byte aclEntry, StackLayer source) {
        this(srcAddr, dstAddr, msdu, mpduLinkQuality,
                Utils.createBitmapSecAndAcl(securityUse.booleanValue(), aclEntry.intValue()), source);
    }

    public MACDataIndication(DataAddress srcAddr, DataAddress dstAddr, Str msdu,
                             UInt mpduLinkQuality, Bitmap secAndAcl) {
        this(srcAddr, dstAddr, msdu, mpduLinkQuality, secAndAcl, null);
    }

    public MACDataIndication(DataAddress srcAddr, DataAddress dstAddr, Str msdu,
                             UInt mpduLinkQuality, Bitmap secAndAcl, StackLayer source) {
        super("macDataIndication", StackEventType.LDE_TYPE, null, source);
        if (srcAddr == null) {
            throw new NullPointerException("srcAddr is null");
        }
        if (dstAddr == null) {
            throw new NullPointerException("dstAddr is null");
        }
        if (msdu == null) {
            throw new NullPointerException("msdu is null");
        }
        if (mpduLinkQuality == null) {
            throw new NullPointerException("mpduLinkQuality is null");
        }
        if (secAndAcl == null) {
            throw new NullPointerException("secAndAcl is null");
        }
        if (!TypeStr.STRING_OCTET.equals(msdu.getType())) {
            throw new IllegalArgumentException("msdu is not an octet string");
        }
        this.srcAddr = srcAddr;
        this.dstAddr = dstAddr;
        this.msduLength = (UInt) TypeUInt.UINT_8.fromObject(new Integer(msdu.getOctetLength()));
        int check = ((Number) msduLength.toObject()).intValue();
        if (check < 0 || check > 102) {
            throw new IllegalArgumentException("invalid msduLength");
        }
        this.msdu = msdu;
        this.mpduLinkQuality = mpduLinkQuality.convert(TypeUInt.UINT_8);
        this.secAndAcl = secAndAcl;
        if (secAndAcl.getBits(5, 8) != 0) {
            throw new IllegalArgumentException("bits 5-7 of secAndAcl are reserved and should be all zeroes");
        }
    }

    public DataAddress getSrcAddr() {
        return srcAddr;
    }

    public DataAddress getDstAddr() {
        return dstAddr;
    }

    public UInt getMsduLengthType() {
        return msduLength;
    }

    public Str getMsduType() {
        return msdu;
    }

    public UInt getMpduLinkQualityType() {
        return mpduLinkQuality;
    }

    public Bitmap getSecAndAclType() {
        return secAndAcl;
    }

    public Byte getMsduLength() {
        return (Byte) msduLength.toObject();
    }

    public byte[] getMsdu() {
        return (byte[]) msdu.toObject();
    }

    public Byte getMpduLinkQuality() {
        return (Byte) mpduLinkQuality.toObject();
    }

    public boolean isSecurityUse() {
        return secAndAcl.getBit(0);
    }

    public int getAclEntry() {
        return secAndAcl.getBits(1, 5);
    }

    public String toString() {
        return getName() + '(' + srcAddr + ',' + dstAddr + ',' + msduLength + ',' + msdu + ',' + mpduLinkQuality + ',' +
                secAndAcl + ')';
    }
}
